//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"
#include "sagittarius/gmp/gmp_set.h"
#include "sagittarius/hash/hash.h"
#include "sagittarius/io/io.h"
#include <malloc.h>
#include <string.h>

inline gmpkv_pair makegmpkv_pair(mpz_t p) {
    gmpkv_pair r;
    mpz_init_set(r.v, p);
    r.k = sg_hash_mpz(r.v);
    return r;
}

int _gmp_set_check(gmp_set* ht, gmpkv_pair e) {
    gmp_set_bucket* b = &ht->buckets[e.k%ht->nbuckets];
    uint32 i;
    
    for(i=0; i<b->n; ++i) { 
        if(mpz_cmp(b->e[i].v, e.v) == 0) {
            return 1;
        }
    }
    
    return 0;
}

int gmp_set_check(gmp_set* ht, mpz_t p) {
    gmpkv_pair z=makegmpkv_pair(p);
    //thanks GMP, for turing this one line function into four lines
    int r=_gmp_set_check(ht, z);
    mpz_clear(z.v);
    return r;
}

int ___valuecompare(mpz_t p, mpz_t q) {
    return mpz_cmp(p, q) == 0;
}

int _gmp_set_insert(gmp_set* ht, gmpkv_pair e) {
    gmp_set_bucket* b = NULL;
    size_t bn;
    gmpkv_pair* tmp;
    uint32 i;
    uint32 depth;
    
     //no modifications to the hash table internals while this is running
    
    bn = e.k%ht->nbuckets;
    b = &ht->buckets[bn]; //NOTE: not safe to do this before ht sema is locked
    
    /*SG_WRITE(2, "  adding: ");
    sg_writeMemSegHex(2, e.v, e.len);
    SG_WRITE(2, "\n");
    SG_WRITE(2, "  key: ");
    sg_writeUInt(2, e.k);
    SG_WRITE(2, "\n");
    SG_WRITE(2, "  bucket number: ");
    sg_writeUInt(2, bn);
    SG_WRITE(2, "\n");*/
    
    for(i=0; i<b->n; ++i) {
        if(___valuecompare(b->e[i].v, e.v)) {
            /*SG_WRITE(2, "  collision with: ");
            sg_writeMemSegHex(2, b->e[i].v, b->e[i].len);
            SG_WRITE(2, "\n");*/
            return HASH_COLLISION;
        }
    }
    if(b->n >= b->depth) {
        //need to reallocate
        depth = b->depth<<1;
        tmp = malloc(depth*sizeof(gmpkv_pair));
        memcpy(tmp, b->e, b->depth*sizeof(gmpkv_pair));
        b->depth = depth;
        free(b->e);
        b->e = tmp;
    }
    b->e[b->n] = e;
    ++b->n;
    
    
    return HASH_SUCCESS;
}

int gmp_set_insert(gmp_set* ht, mpz_t p) {
    return _gmp_set_insert(ht, makegmpkv_pair(p));
}

gmp_set* gmp_set_rehash(gmp_set* ht, uint32 nbuckets, uint32 depth) {
    gmp_set* h;
    gmp_set_bucket* b;
    uint32 i, j;
    
    h = malloc(sizeof(gmp_set));
    AN(h);
    
    h->nbuckets = nbuckets;
    h->buckets = malloc(nbuckets*sizeof(gmp_set_bucket));
    for(i=0; i<h->nbuckets; ++i) {
        h->buckets[i].n = 0;
        h->buckets[i].depth = depth;
        h->buckets[i].e = malloc(depth*sizeof(gmpkv_pair));
        
    }
    
    //insert old values
    if(ht != NULL) {
        for(i=0; i<ht->nbuckets; ++i) {
            b = &ht->buckets[i];
            for(j=0; j<b->n; ++j) {
                _gmp_set_insert(h, b->e[j]);
            }
        }
    }
    
    return h;
}

void gmp_bucket_destoy(gmp_set_bucket* b) {
    uint32 i;
    for(i=0; i<b->n; ++i) {
        mpz_clear(b->e[i].v);
    }
    free(b->e);
}

void gmp_set_destroy(gmp_set* h) {
    uint32 i;
    for(i=0; i<h->nbuckets; ++i) {
        gmp_bucket_destoy(&h->buckets[i]);
    }
    free(h->buckets);
    free(h);
}

void gmp_set_dumpstate(int fd, gmp_set* h) {
    uint32 i, j;
    SG_WRITE(fd, "GMP Set");
    SG_WRITE(fd, "\n  Number of buckets: ");
    sg_writeInt(fd, (int)h->nbuckets);
    SG_WRITE(fd, "\n");
    for(i=0; i<h->nbuckets; ++i) {
        SG_WRITE(fd, "    Bucket ");
        sg_writeUInt(fd, i);
        SG_WRITE(fd, "\n      Number ");
        sg_writeUInt(fd, h->buckets[i].n);
        SG_WRITE(fd, "\n      Depth ");
        sg_writeUInt(fd, h->buckets[i].depth);
        SG_WRITE(fd, "\n");
        
        for(j=0; j<h->buckets[i].n; ++j) {
            SG_WRITE(fd, "        Key ");
            sg_writeMemSegHex(fd, &h->buckets[i].e[j].k, 8);
            SG_WRITE(fd, "\n        Value ");
            sg_writeMemSegHex(fd, h->buckets[i].e[j].v, 8);
            SG_WRITE(fd, "\n");
        }
    }
}